package glib

// #include <glib.h>
// #include <glib-object.h>
// #include "glib.go.h"
import "C"
import (
	"reflect"
	"unsafe"

	"github.com/gotk3/gotk3/internal/closure"
)

/*
 * Events
 */

// SignalHandle is the ID of a signal handler.
type SignalHandle uint

// Connect is a wrapper around g_signal_connect_closure(). f must be a function
// with at least one parameter matching the type it is connected to.
//
// It is optional to list the rest of the required types from Gtk, as values
// that don't fit into the function parameter will simply be ignored; however,
// extraneous types will trigger a runtime panic. Arguments for f must be a
// matching Go equivalent type for the C callback, or an interface type which
// the value may be packed in. If the type is not suitable, a runtime panic will
// occur when the signal is emitted.
//
// Circular References
//
// To prevent circular references, prefer declaring Connect functions like so:
//
//    obj.Connect(func(obj *ObjType) { obj.Do() })
//
// Instead of directly referencing the object from outside like so:
//
//    obj.Connect(func() { obj.Do() })
//
// When using Connect, beware of referencing variables outside the closure that
// may cause a circular reference that prevents both Go from garbage collecting
// the callback and GTK from successfully unreferencing its values.
//
// Below is an example piece of code that is considered "leaky":
//
//    type ChatBox struct {
//        gtk.TextView
//        Loader *gdk.PixbufLoader
//
//        State State
//    }
//
//    func (box *ChatBox) Method() {
//        box.Loader.Connect("size-allocate", func(loader *gdk.PixbufLoader) {
//            // Here, we're dereferencing box to get the state, which might
//            // keep box alive along with the PixbufLoader, causing a circular
//            // reference.
//            loader.SetSize(box.State.Width, box.State.Height)
//        })
//    }
//
// 有很多解决方案可以修复上述代码段。例如，
// box.Loader 可以在完成后立即通过将其设置为 nil 来手动丢弃，或者可以手动断开信号句柄，或者可以在回调中第一次调用后将 box 设置为 nil。
//detailedSignal 详细信号
func (v *Object) Connect(detailedSignal string, f interface{}) SignalHandle {
	return v.connectClosure(false, detailedSignal, f)
}

// ConnectAfter 是 g_signal_connect_closure() 的包装器。 Connect 和 ConnectAfter 之间的区别在于后者将在默认处理程序之后调用，而不是之前。有关更多信息，请参阅连接。
func (v *Object) ConnectAfter(detailedSignal string, f interface{}) SignalHandle {
	return v.connectClosure(true, detailedSignal, f)
}

// ClosureCheckReceiver，如果为真，将让 GLib 检查每个闭包的第一个参数以确保它是正确的，否则它会恐慌并显示有关可能的循环引用的消息警告。在这种情况下，接收者通常是回调的第一个参数。
//
// 可以通过使用 go.mod 的 replace 指令来更改此常量以进行调试。
const ClosureCheckReceiver = false

func (v *Object) connectClosure(after bool, detailedSignal string, f interface{}) SignalHandle {
	fs := closure.NewFuncStack(f, 2)

	if ClosureCheckReceiver {
		// 这有点慢，但我们可以小心。
		objValue, err := v.goValue()
		if err == nil {
			fsType := fs.Func.Type()
			if fsType.NumIn() < 1 {
				fs.Panicf("回调应该有对象接收器以避免循环引用")
			}
			objType := reflect.TypeOf(objValue)
			if first := fsType.In(0); !objType.ConvertibleTo(first) {
				fs.Panicf("接收器无法转换为预期类型 %s，得到 %s", objType, first)
			}
		}

		// 如果我们无法获得值封送拆收器，则允许类型检查失败。这种情况很少发生，但可能会发生，我们希望至少允许解决它。
	}

	cstr := C.CString(detailedSignal)
	defer C.free(unsafe.Pointer(cstr))

	gclosure := ClosureNewFunc(fs)
	c := C.g_signal_connect_closure(C.gpointer(v.native()), (*C.gchar)(cstr), gclosure, gbool(after))

	// TODO：这里有一个轻微的竞争条件，其中 g_signal_connect_closure 可能会在信号注册之前触发信号回调。因此，理想的做法是将另一个中间 ID 传递给连接函数。不过，这不是什么大问题，因为在 Connect 函数成功返回之前，并不能真正保证信号应该到达。
	closure.RegisterSignal(uint(c), unsafe.Pointer(gclosure))

	return SignalHandle(c)
}

// ClosureNew 创建一个新的 GClosure 并将其回调函数添加到内部注册表中。它被导出以供其他 gotk3 包可见，不应在常规应用程序中使用。
func ClosureNew(f interface{}) *C.GClosure {
	return ClosureNewFunc(closure.NewFuncStack(f, 2))
}

// ClosureNewFunc creates a new GClosure and adds its callback function to the
// internal registry. It's exported for visibility to other gotk3 packages; it
// cannot be used in application code, as package closure is part of the
// internals.
func ClosureNewFunc(funcStack closure.FuncStack) *C.GClosure {
	gclosure := C._g_closure_new()
	closure.Assign(unsafe.Pointer(gclosure), funcStack)

	return gclosure
}

// removeClosure removes a closure from the internal closures map. This is
// needed to prevent a leak where Go code can access the closure context
// (along with rf and userdata) even after an object has been destroyed and
// the GClosure is invalidated and will never run.
//
//export removeClosure
func removeClosure(_ C.gpointer, gclosure *C.GClosure) {
	closure.Delete(unsafe.Pointer(gclosure))
}
